Skip to content

Two-tier theme picker + Material 3 dark themes (Dark High Contrast, Dracula, Nord, Monokai, Ayu, Catppuccin)#57

Merged
hawkff merged 15 commits into
mainfrom
feature/m3-dark-high-contrast
Jun 22, 2026
Merged

Two-tier theme picker + Material 3 dark themes (Dark High Contrast, Dracula, Nord, Monokai, Ayu, Catppuccin)#57
hawkff merged 15 commits into
mainfrom
feature/m3-dark-high-contrast

Conversation

@hawkff

@hawkff hawkff commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a modern theme system on top of the Material 3 migration: a two-tier picker and six full-fledged M3 dark themes, surfaced by name.

Phase 1 — Two-tier theme picker

  • New ThemePickerPreference opens a modern named list of M3 themes (preview swatch + name), with a trailing "Classic colors…" row that opens the original swatch grid for the legacy single-accent palettes.
  • Theme.MODERN_THEMES is a declarative catalog; the classic grid skips any id in the modern list (no duplication). Removes the superseded ColorPickerPreference.
  • Persistence unchanged: same appTheme int key.

Phase 2 — Dark High Contrast (OLED)

  • Full M3 dark-only theme on a true black (#000000) canvas: green-primary action accent (GitHub style), bright blue secondary, high-contrast near-white text.
  • M3 black chrome (app bar / bottom bar / status & nav bars use the dark surface, not the accent), so blue/green/red status text reads clearly.
  • Dark-only night-mode handling generalized to Theme.DARK_ONLY_THEMES (forces night on, restores prior setting on exit; storage key kept for back-compat).
  • New fabConnectedColor attr fixes the connect FAB airplane vanishing when connected (green icon on a green FAB) — icon stays visible.

Phase 3 — More M3 dark themes

  • Dracula (M3, separate from legacy Dracula id 23), Nord, Monokai, Ayu (Mirage), Catppuccin (Mocha) — official palettes, each with full dark canvas, M3 roles, black chrome, status semantics, and visible FAB icon.
  • Crash fix: appBarBackgroundColor resolves to a concrete @color per theme (android:background can't follow attr-to-attr indirection).

Testing

  • NAMESPACE CI build.
  • On-device (Android): two-tier picker selects every theme; all six modern themes apply with correct accents, dark chrome, and a visible connect-FAB icon across stopped/connected states; legacy themes unchanged; no crashes.
  • CodeRabbit CLI + Greptile addressed.
  • Verified on Android.

Greptile Summary

This PR adds a two-tier theme picker and six full Material 3 dark themes (Dark High Contrast, Dracula M3, Nord, Monokai, Ayu, Catppuccin) on top of the existing M3 migration, while also fixing the connected-FAB icon visibility and the AppBarLayout attr-indirection crash.

  • Two-tier picker (ThemePickerPreference): a modern named list (swatch + label, ScrollView-wrapped) with a "Classic colors…" row that opens the original swatch grid; ColorPickerPreference removed.
  • Dark-only theme generalization: Dracula's forced-night-mode handling is extended to all six new themes via DARK_ONLY_THEMES; nightThemeBeforeDracula store key kept for backward compatibility.
  • Layout M3 polish: CheckBoxMaterialCheckBox, SwitchCompatMaterialSwitch, appBarBackgroundColor attr replaces ?colorPrimary on every toolbar/tab strip (fixing the inflation crash), and a FAB scroll-hide listener is added to ConfigurationFragment.

Confidence Score: 4/5

Safe to merge with one logic fix: the night-mode coercion in SettingsPreferenceFragment silently no-ops when switching between two dark-only themes while night mode is not active.

The night-mode guard on line 82 uses !leavingDarkOnly as a combined condition that both skips saving the restore value and skips forcing night on. When the user is on a dark-only theme, manually overrides night mode to light, and then switches to another dark-only theme, night mode is never re-enabled — leaving the new theme's dark canvas inactive. All other changes (theme XML, picker UI, FAB tint, layout migrations) look correct.

app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt — the enteringDarkOnly branch (lines 81–90)

Important Files Changed

Filename Overview
app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt Night mode coercion logic generalized from DRACULA to DARK_ONLY_THEMES, but the guard condition silently skips forcing night mode when switching between two dark-only themes after a manual night-mode override.
app/src/main/java/io/nekohasekai/sagernet/utils/Theme.kt Adds six new theme constants and DARK_ONLY_THEMES set; MODERN_THEMES catalog drives the named picker; both getTheme/getDialogTheme switch arms updated. Duplicate KDoc block above ThemeInfo is a minor style issue.
app/src/main/java/moe/matsuri/nb4a/ui/ThemePickerPreference.kt New two-tier picker: modern named list (ScrollView-wrapped) + legacy color grid; holder reuse keyed off childCount; ringedSwatchView for OLED-black identity. Logic and view lifecycle look correct.
app/src/main/res/values-night/themes.xml Six full dark-canvas M3 theme variants added; appBarBackgroundColor resolved to a concrete @color per theme to avoid attr-to-attr indirection crash on AppBarLayout inflation.
app/src/main/res/values/themes.xml Day-side counterparts for all six new themes declared; appBarBackgroundColor attr introduced as concrete @color on base theme and all variants; fabConnectedColor added.
app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt Connected-state FAB icon now reads fabConnectedColor instead of statusConnectedColor, allowing themes with a green FAB background to override to a contrasting icon color.
app/src/main/java/io/nekohasekai/sagernet/ui/ConfigurationFragment.kt Adds RecyclerView scroll listener to auto-hide/show the FAB during scroll; only acts in stable service states to avoid fighting transient animations.
app/src/main/java/io/nekohasekai/sagernet/ui/ThemedActivity.kt Bottom inset now also applied to the stats bar so the FAB progress ring isn't clipped by the navigation bar under edge-to-edge.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User selects theme] --> B{enteringDarkOnly?}
    B -- No --> C{leavingDarkOnly?}
    C -- No --> Z[Apply theme, recreate activity]
    C -- Yes --> D[Restore nightThemeBeforeDracula if != -1]
    D --> Z
    B -- Yes --> E{!leavingDarkOnly AND nightTheme != 1?}
    E -- Yes --> F[Save nightThemeBeforeDracula / Force nightTheme=1 / applyNightTheme]
    F --> Z
    E -- No --> G[No-op: night mode not forced on]
    G --> Z
    style G fill:#ff9999,stroke:#cc0000
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[User selects theme] --> B{enteringDarkOnly?}
    B -- No --> C{leavingDarkOnly?}
    C -- No --> Z[Apply theme, recreate activity]
    C -- Yes --> D[Restore nightThemeBeforeDracula if != -1]
    D --> Z
    B -- Yes --> E{!leavingDarkOnly AND nightTheme != 1?}
    E -- Yes --> F[Save nightThemeBeforeDracula / Force nightTheme=1 / applyNightTheme]
    F --> Z
    E -- No --> G[No-op: night mode not forced on]
    G --> Z
    style G fill:#ff9999,stroke:#cc0000
Loading

Reviews (8): Last reviewed commit: "Hide FAB on scroll + 168dp list padding ..." | Re-trigger Greptile

hawkff added 2 commits June 22, 2026 14:16
Replace the single color-swatch grid with a ThemePickerPreference that
opens a modern named list of full-fledged Material 3 themes (preview
swatch + name), with a trailing 'Classic colors...' row that opens the
original swatch grid for the legacy single-accent palettes.

- New ThemePickerPreference supersedes ColorPickerPreference (removed).
- Theme.MODERN_THEMES declarative catalog (Dynamic for now); legacy
  grid skips any id present in the modern list so there's no duplication.
- Persistence unchanged: same appTheme int key, same persist-then-notify
  ordering, so theme apply/recreate behavior is identical.

No theme behavior changes; this is an isolated picker refactor.
Address CodeRabbit findings:
- Use safe cast (as?) with early return for the widget_frame lookup.
- Add contentDescription to modern list rows (theme name) and classic
  grid swatches (Color theme %d) for screen-reader support.
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

ColorPickerPreference is removed and replaced by ThemePickerPreference, which introduces a Theme.ThemeInfo metadata model and MODERN_THEMES list containing both existing and new modern themes. The new preference shows a two-tier Material dialog (named modern themes plus legacy color grid fallback) and persists the selected theme ID. A new Dark High Contrast theme with Material 3 OLED-suitable colors is added alongside five additional modern theme palettes (Dracula M3, Nord, Monokai, Ayu, Catppuccin). Night-mode logic in SettingsPreferenceFragment is generalized from Dracula-only to all dark-only themes. Two new theme attributes (appBarBackgroundColor, fabConnectedColor) are introduced and applied throughout AppBar, layout, FAB, and tab components. Preference XML and settings fragment are updated accordingly.

Changes

Theme System Expansion

Layer / File(s) Summary
Theme constants and metadata model
app/src/main/java/io/nekohasekai/sagernet/utils/Theme.kt
Adds DARK_HIGH_CONTRAST theme id constant (25) to DARK_ONLY_THEMES; introduces ThemeInfo data class (id, nameRes, previewColor); defines MODERN_THEMES list with DARK_HIGH_CONTRAST, DYNAMIC, and other modern entries; extends getTheme() and getDialogTheme() to map modern theme ids including DARK_HIGH_CONTRAST, DRACULA_M3, NORD, MONOKAI, AYU, and CATPPUCCIN to dedicated style resources.
Dark-only themes night-mode generalization
app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt, app/src/main/java/io/nekohasekai/sagernet/Constants.kt, app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt
Expands theme-change listener from Dracula-only to Theme.DARK_ONLY_THEMES approach: forces night mode when entering any dark-only theme, restores prior value when leaving; updates comments in Constants and DataStore to clarify generalized dark-only-theme semantics, restore behavior, and backward compatibility with stored keys.
ThemePickerPreference implementation
app/src/main/java/moe/matsuri/nb4a/ui/ThemePickerPreference.kt
New ThemePickerPreference extending Preference: binds tinted swatch preview in onBindViewHolder, overrides onClick to launch showModernDialog() (one named row per MODERN_THEMES entry plus "Classic colors…" row), implements buildRow() for accessible ripple-background selection rows, showClassicGrid() for legacy 4-column swatch grid (deduplicating modern theme IDs), and select() to conditionally persist and broadcast chosen theme ID.
Settings preference and fragment integration
app/src/main/res/xml/global_preferences.xml, app/src/main/res/values/strings.xml
Replaces ColorPickerPreference with ThemePickerPreference in general settings XML; adds nine theme label string resources: theme_dynamic, theme_dark_high_contrast, theme_dracula_m3, theme_nord, theme_monokai, theme_ayu, theme_catppuccin, theme_classic_colors, and theme_classic_color_swatch.

Theme Visual Definition and Layout Updates

Layer / File(s) Summary
Theme attributes and modern theme color palettes
app/src/main/res/values/attrs.xml, app/src/main/res/values/colors.xml
Adds fabConnectedColor attribute (FAB connected-icon tint, defaults to statusConnectedColor for backward compatibility) and appBarBackgroundColor attribute (app bar and status bar background, with M3 dark theme defaults); defines Dark High Contrast (dhc_\*) color palette for true OLED/black canvas and five modern Material 3 dark palettes (draculam3, nord, monokai, ayu, catppuccin), each with primary/secondary/tertiary, surfaces, outlines, elevated cards, and app-specific semantic status/protocol/speed colors.
Dark High Contrast and modern theme styles with night variants
app/src/main/res/values/themes.xml, app/src/main/res/values-night/themes.xml
Updates Theme.SagerNet and Theme.SagerNet.Dialog base styles to include fabConnectedColor and appBarBackgroundColor attributes; adds complete Theme.SagerNet.DarkHighContrast and dialog variant using dhc_\* colors for true OLED black canvas with full Material 3 role overrides; adds night-only DarkHighContrast variants and updates Dracula themes with appBarBackgroundColor; extends appBarBackgroundColor across many palette theme variants (including modern theme variants) to match each palette's background/primary colors; adds Widget.SagerNet.BottomAppBar.DarkHighContrast and palette-specific bottom bar widget styles; adds appBarBackgroundColor to Theme.Start as transparent.
ServiceButton FAB tint and AppBar layout background updates
app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt, app/src/main/java/io/nekohasekai/sagernet/ui/ThemedActivity.kt, app/src/main/res/layout/layout_app_list.xml, app/src/main/res/layout/layout_appbar.xml, app/src/main/res/layout/layout_apps.xml, app/src/main/res/layout/layout_group_list.xml, app/src/main/res/layout/layout_main.xml, app/src/main/res/layout/layout_tools.xml, app/src/main/res/layout/layout_webdav_settings.xml
Updates ServiceButton.applyStateTint() FAB connected-state tint to use new fabConnectedColor attribute; eight layout files switch AppBarLayout, MaterialToolbar, TabLayout, and StatsBar backgrounds from ?attr/colorPrimary to ?attr/appBarBackgroundColor for semantic consistency across all themes; ThemedActivity adds bottom padding to stats view to avoid navigation bar clipping with edge-to-edge content.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • hawkff/NekoBoxForAndroid#38: Modifies SettingsPreferenceFragment night-mode state handling around "nightThemeBeforeDracula" restore logic; the main PR generalizes that Dracula-specific behavior to all dark-only themes.
  • hawkff/NekoBoxForAndroid#41: Updates ServiceButton FAB tinting with theme-driven semantic color attributes; the main PR's new fabConnectedColor attribute extends that semantic color infrastructure.
  • hawkff/NekoBoxForAndroid#56: Introduces Material 3 theme system with Theme.DYNAMIC; the main PR adds DARK_HIGH_CONTRAST and extends Theme.MODERN_THEMES to include DYNAMIC, building directly on that foundation.

Poem

🐇 Dark contrast blooms in OLED black,
Two-tier picker brings themes right back!
Modern lists with classic colors blend,
App bars shimmer—where styles transcend.
The rabbit's palette choice shines bright,
Six new themes transform day into night! 🎨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: a two-tier theme picker UI replacement and six new Material 3 dark themes with specific names.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description is directly related to the changeset: it describes the new theme picker, six M3 dark themes, related UI/logic changes, and testing performed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

Comment thread app/src/main/java/moe/matsuri/nb4a/ui/ThemePickerPreference.kt
Comment thread app/src/main/java/moe/matsuri/nb4a/ui/ThemePickerPreference.kt
hawkff added 3 commits June 22, 2026 14:31
Address Greptile review:
- Wrap the modern theme list in a ScrollView so it stays usable on
  compact screens as Theme.MODERN_THEMES grows.
- Bind the preference swatch off the holder's actual view state
  (widgetFrame.childCount) instead of an instance flag, so a recycled/
  re-inflated holder still gets its swatch. Drops the unused 'inited' field.
A full-fledged M3 dark-only theme on a true black (#000000) canvas:
green-primary action accent (GitHub style) with a bright blue secondary
for links/selection and high-contrast near-white text.

- Theme.DARK_HIGH_CONTRAST (id 25) mapped to new Theme.SagerNet.
  DarkHighContrast styles (day declares the full dark canvas so it never
  flashes light; night repeats it as the active forced-night path).
- Full M3 role overrides: surface/background/containers, surfaceVariant,
  outline/outlineVariant, secondary/tertiary, plus status semantics
  (green/red/yellow) and route/proxy blue.
- Generalize the dark-only night-mode handling: selecting any theme in
  Theme.DARK_ONLY_THEMES (Dracula, Dark High Contrast) forces night mode
  on and restores the prior setting on exit. Storage key kept for compat.
- Surfaced by name in the modern picker list (green preview swatch);
  excluded from the classic grid.
- Picker select() now follows the standard Preference contract
  (notify-then-persist) so the fragment sees the true previous theme.
Refine the OLED theme to match the intended high-contrast design and fix
two issues seen on-device:

- App bar / bottom status bar / status & nav bars now use the black
  surface instead of the green primary, via a new themable
  appBarBackgroundColor attr (defaults to colorPrimary, so all other
  themes are unchanged) wired into the shared appbar/toolbar/tab layouts.
  This reserves green for actions/selection and lets the blue/green/red
  status text read against black chrome (previously the cyan/blue detail
  text was washed out on the green bar).
- Fix the connect FAB airplane vanishing when connected: the green
  connected-icon tint was invisible on the green FAB. Add a dedicated
  fabConnectedColor attr (defaults to statusConnectedColor for back-compat)
  and set it to the dark on-primary color for Dark High Contrast so the
  icon stays visible, matching the stopped state.
- Black DHC bottom-bar style, dark ActionBar overlay, green tab
  selection/indicator on the OLED canvas.

Verified on Android across stopped/connected states.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt`:
- Around line 74-82: The variable previousTheme is assigned from
DataStore.appTheme on line 74, but because the preference change has already
been persisted before this listener is called (persist-then-notify contract),
DataStore.appTheme already contains the new themeId value, not the old one. This
causes previousTheme to equal themeId, making the enteringDarkOnly and
leavingDarkOnly detection incorrect and potentially skipping both force and
restore branches. Capture the actual previous theme value before the preference
gets persisted, possibly by storing it in a field before the preference change
occurs or by obtaining it through a mechanism that provides the prior value
before the persistence happens.

In `@app/src/main/res/values/themes.xml`:
- Around line 881-918: The Theme.SagerNet.Dialog.DarkHighContrast style is
missing an override for fabConnectedColor, which causes the connected FAB icon
to lose contrast since it uses the same green color as the FAB background. Add a
new item element with name="fabConnectedColor" set to "`@color/dhc_on_primary`"
inside the Theme.SagerNet.Dialog.DarkHighContrast style block, matching the
approach already used in the base Theme.SagerNet.DarkHighContrast theme. Apply
this same change to both the values/themes.xml and values-night/themes.xml
files.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c6c8c0d2-26c8-41a1-a29a-2e158345aee0

📥 Commits

Reviewing files that changed from the base of the PR and between 37661e1 and 67636f3.

📒 Files selected for processing (18)
  • app/src/main/java/io/nekohasekai/sagernet/Constants.kt
  • app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt
  • app/src/main/java/io/nekohasekai/sagernet/ui/SettingsPreferenceFragment.kt
  • app/src/main/java/io/nekohasekai/sagernet/utils/Theme.kt
  • app/src/main/java/io/nekohasekai/sagernet/widget/ServiceButton.kt
  • app/src/main/java/moe/matsuri/nb4a/ui/ThemePickerPreference.kt
  • app/src/main/res/layout/layout_app_list.xml
  • app/src/main/res/layout/layout_appbar.xml
  • app/src/main/res/layout/layout_apps.xml
  • app/src/main/res/layout/layout_group_list.xml
  • app/src/main/res/layout/layout_main.xml
  • app/src/main/res/layout/layout_tools.xml
  • app/src/main/res/layout/layout_webdav_settings.xml
  • app/src/main/res/values-night/themes.xml
  • app/src/main/res/values/attrs.xml
  • app/src/main/res/values/colors.xml
  • app/src/main/res/values/strings.xml
  • app/src/main/res/values/themes.xml
✅ Files skipped from review due to trivial changes (9)
  • app/src/main/res/layout/layout_appbar.xml
  • app/src/main/java/io/nekohasekai/sagernet/Constants.kt
  • app/src/main/res/layout/layout_group_list.xml
  • app/src/main/res/layout/layout_apps.xml
  • app/src/main/java/io/nekohasekai/sagernet/database/DataStore.kt
  • app/src/main/res/layout/layout_main.xml
  • app/src/main/res/layout/layout_webdav_settings.xml
  • app/src/main/res/layout/layout_app_list.xml
  • app/src/main/res/values/strings.xml
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/src/main/java/moe/matsuri/nb4a/ui/ThemePickerPreference.kt

Comment thread app/src/main/res/values/themes.xml
hawkff added 2 commits June 22, 2026 15:16
Five new full-fledged M3 dark themes surfaced by name in the modern
picker (legacy Dracula id 23 left untouched):
- Dracula-M3 (id 26), Nord (27), Monokai (28), Ayu (29), Catppuccin (30).
- Each: official palette in colors.xml + day/night styles with the full
  dark canvas, M3 role overrides, black chrome (surface app/bottom bars,
  dark ActionBar overlay, themed tabs), status semantics, and a visible
  connected FAB icon (fabConnectedColor = on-primary). Registered in
  Theme.kt (ids, style maps, MODERN_THEMES, DARK_ONLY_THEMES) + strings.

Fix app-bar inflation crash: appBarBackgroundColor must resolve to a
CONCRETE @color, not an attr reference. android:background reads it as a
drawable via getDrawableForDensity, which does not follow attr-to-attr
indirection (?attr -> ?attr crashed AppBarLayout/BottomAppBar inflation).
Each theme now sets appBarBackgroundColor to a concrete color matching its
colorPrimary (legacy) or dark canvas (modern). StatsBar drops its explicit
backgroundTint and inherits the per-theme bottomAppBarStyle instead.

Verified on Android: all 6 modern themes apply with correct accents, dark
chrome, visible FAB icon, no crashes; legacy themes unchanged.
…olor

- Correct Catppuccin Mocha palette to official values: surface = mantle
  (#181825), surfaceVariant = surface0 (#313244), and primary_dark to a
  real palette color (sapphire #74C7EC) instead of a custom mauve tint.
- Add the missing fabConnectedColor to the Dark High Contrast dialog
  styles (day + night) for consistency with the app variants and the
  other modern dialog themes.

(CodeRabbit's 'missing theme styles' findings were false positives \u2014 all
12 styles exist and were verified building and on-device.)
@hawkff hawkff changed the title Two-tier theme picker (modern named list + Classic colors grid) Two-tier theme picker + Material 3 dark themes (Dark High Contrast, Dracula, Nord, Monokai, Ayu, Catppuccin) Jun 22, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/main/res/values/themes.xml`:
- Around line 258-273: The BottomAppBar variant styles
(Widget.SagerNet.BottomAppBar.DraculaM3, Widget.SagerNet.BottomAppBar.Nord,
Widget.SagerNet.BottomAppBar.Monokai, Widget.SagerNet.BottomAppBar.Ayu, and
Widget.SagerNet.BottomAppBar.Catppuccin) are missing parent attribute
declarations. Add parent="Widget.SagerNet.BottomAppBar" to each of these style
definitions so they inherit the shared base widget configuration (alignment,
cradle, elevation) while only overriding the backgroundTint attribute.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 649f5d91-b6c9-41bf-97ae-2331d274b54a

📥 Commits

Reviewing files that changed from the base of the PR and between 67636f3 and 85048c7.

📒 Files selected for processing (7)
  • app/src/main/java/io/nekohasekai/sagernet/utils/Theme.kt
  • app/src/main/res/layout/layout_main.xml
  • app/src/main/res/values-night/themes.xml
  • app/src/main/res/values/attrs.xml
  • app/src/main/res/values/colors.xml
  • app/src/main/res/values/strings.xml
  • app/src/main/res/values/themes.xml
💤 Files with no reviewable changes (1)
  • app/src/main/res/layout/layout_main.xml
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/src/main/res/values/attrs.xml
  • app/src/main/res/values-night/themes.xml

Comment thread app/src/main/res/values/themes.xml Outdated
hawkff added 2 commits June 22, 2026 15:30
Address review feedback:
- Dracula-M3: profile names now use Dracula yellow (#F1FA8C) instead of
  white, matching the authentic Dracula look.
- Dark High Contrast: the bottom-bar speed arrows and the 'tap to check
  connection' detail text now use a muted near-white (on-surface-variant)
  instead of bright blue \u2014 a more restrained OLED palette in the spirit of
  F-Droid's true-black theme; blue stays reserved for links/route-proxy.
- Lift the bottom status bar (and the docked FAB + its anchored progress
  ring) above the navigation-bar inset under edge-to-edge, so the loading
  ring is no longer clipped by the system inset.

Verified on Android (Dracula-M3 + Dark High Contrast).
Give the modern themes' Widget.SagerNet.BottomAppBar.* variants an
explicit parent=Widget.SagerNet.BottomAppBar so they keep the shared
cradle/alignment/elevation config and only override backgroundTint,
rather than relying on implicit dotted-name inheritance.
@hawkff

hawkff commented Jun 22, 2026

Copy link
Copy Markdown
Owner Author

Re: the previousTheme / dark-only-transition finding — the picker's select() uses the notify-then-persist order (callChangeListener first, then persistInt only if accepted), so DataStore.appTheme still holds the previous theme when the change listener runs. previousTheme is therefore correct, and the dark-only force/restore branches fire as intended. Verified on-device: selecting Dark High Contrast / Dracula-M3 forces night mode on, and leaving restores the prior night setting.

hawkff added 6 commits June 22, 2026 15:55
Address review feedback:
- Dark High Contrast: use green (colorPrimary) for colorAccent/colorSecondary
  so section headers (App Settings / Route Settings) and switch toggles are
  green, matching the green identity (and the theme's green picker swatch),
  instead of the previously confusing blue. Blue (colorTertiary / route
  proxy / links) stays reserved for links.
- Rename the picker's 'Classic colors...' entry to 'Legacy colors...'.
- Add top padding + spacing above the connection-test progress dialog's
  loading circle so it isn't flush against the dialog's top edge.

Verified on Android (DHC: green header + green toggle).
Replace the last non-M3 themed widgets so the app is fully Material 3:
- SwitchCompat -> MaterialSwitch in layout_route_item and layout_apps_item.
- CheckBox -> MaterialCheckBox in layout_backup and layout_import (6 total).
- androidx.appcompat.widget.Toolbar -> MaterialToolbar in webdav settings.

All are API-compatible subclasses, so ViewBinding/findViewById and the
existing isChecked/listener calls are unchanged. Only AppCompatImageView
remains, which is the idiomatic M3 image view (not an M2 themed widget).

Verified on Android: route/apps MaterialSwitch toggles render M3 (green
on Dark High Contrast), no crashes.
- Theme picker: Dark High Contrast preview swatch is now a black circle
  with a white circumference ring (via ThemeInfo.ringColor + a
  GradientDrawable oval) so it reads as 'OLED black', not 'a green theme'.
  Other modern themes keep their solid accent dot.
- Profile + route lists: increase bottom padding to 96dp (with
  clipToPadding=false) so the last card clears the docked FAB and the
  bottom stats bar instead of being hidden behind them when scrolled to
  the bottom.

Verified on Android (DHC swatch ring; last card clears FAB at scroll end).
96dp wasn't enough: when connected, the pinned stats bar (~115dp) plus
the FAB cradle overhang (~40dp) overlapped the last card. Measured the
FAB top sitting ~431px above the screen bottom on the test device, so
bump the profile + route list bottom padding to 150dp (with
clipToPadding=false) so the last card always clears the FAB and stats
bar in both connected and disconnected states.

Verified on Android: last card fully visible above the FAB at scroll end.
- Add a RecyclerView scroll listener on the profile list: hide the docked
  FAB on downward scroll, show it on upward scroll or when the list
  settles (idle). Only acts in stable Stopped/Connected states so it
  doesn't fight the FAB's connect/disconnect animation.
- Bump profile + route list bottom padding to 168dp (FAB occupies ~157dp
  at 440dpi) so the last card always rests above the docked FAB and the
  pinned stats bar, in both connected and disconnected states.

Verified on Android (Dracula-M3, connected): last card clears the FAB at
the scroll extent; FAB tucks away while scrolling.
@hawkff hawkff merged commit e7a1cea into main Jun 22, 2026
5 checks passed
@hawkff hawkff deleted the feature/m3-dark-high-contrast branch June 23, 2026 00:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant